﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using RPGXEngine.Scripting.SystemFunctions;
using RPGXEngine.Factories;

namespace RPGXEngine
{
    public class ScriptManager
    {
        #region Events

        public delegate void LoadExtraSystemFunctionsDelegate(ScriptManager sm);
        public event LoadExtraSystemFunctionsDelegate LoadExtraSystemFunctions;

        #endregion

        #region Properties

        public string ScriptDirectory { get; private set; }

        public List<Script> Scripts
        {
            get { return _scripts; }
            private set { _scripts = value; }
        }
        private List<Script> _scripts = new List<Script>();

        public ScriptDataTree DataTree
        {
            get { return _dataTree; }
            private set { _dataTree = value; }
        }
        private ScriptDataTree _dataTree = new ScriptDataTree();

        private int Function_OnLoadAddress { get; set; }

        public IVariableType Zero
        {
            get
            {
                IVariableType zero = VariableTypeFactory.Create(DataType.Var, String.Empty, 0);
                return zero;
            }
        }

        public IVariableType True
        {
            get
            {
                IVariableType t = VariableTypeFactory.Create(DataType.Var, String.Empty, true);
                return t;
            }
        }

        public IVariableType False
        {
            get
            {
                IVariableType f = VariableTypeFactory.Create(DataType.Var, String.Empty, false);
                return f;
            }
        }

        #endregion

        #region Public Methods

        public void Load(string scriptDirectory)
        {
            this.UnLoad();

            var files = DirectoryFactory.Get().GetFiles(scriptDirectory, "*.spt");
            foreach (var f in files)
            {
                Script s = new Script();

                if (s.Load(f, this))
                    this.Scripts.Add(s);
            }

            this.DataTree.AddressFunctions(this.Scripts);

            this.InitSystemFunctions();

            if (this.LoadExtraSystemFunctions != null)
                this.LoadExtraSystemFunctions(this);

            ScriptParser sp = new ScriptParser();
            sp.ProcessFunctionBodyParts(this);

            this.FindTopLevelFunctions();
        }

        public void UnLoad()
        {
            foreach (var s in this.Scripts)
            {
                s.UnLoad();
            }

            this.Scripts.Clear();
        }

        public void Run_OnLoad()
        {
            if (this.Function_OnLoadAddress >= 0)
                this.DataTree.FunctionAddressIndex[this.Function_OnLoadAddress].Execute(this);
        }

        public void AddSystemFunction(string name, List<IVariableType> args, SystemFunction.SystemFunctionDelegate function)
        {
            Function f = new Function();

            f.Name = name;

            f.IsSystemFunction = true;
            f.SystemFunction = function;

            f.Arguments.AddRange(args);

            this.DataTree.Functions.Add(f);

            this.DataTree.FunctionNameIndex.Add(f.Name, f);

            int address = this.DataTree.Functions.Count - 1;

            this.DataTree.FunctionAddressIndex.Add(address, f);
            this.DataTree.FunctionNameAddressIndex.Add(f.Name, address);
        }

        public void RunCode(string code)
        {
            if (String.IsNullOrEmpty(code.Trim()))
                return;

            var cmd = CommandFactory.Create(code, this);

            if (cmd != null)
                cmd.Execute(this, new FunctionScope());
        }

        #endregion

        #region Private Methods

        private void FindTopLevelFunctions()
        {
            if (this.DataTree.FunctionNameIndex.ContainsKey("onload"))
                this.Function_OnLoadAddress = this.DataTree.FunctionNameAddressIndex["onload"];
        }

        private void InitSystemFunctions()
        {
            #region System Functions

            #region Array Functions

            this.AddSystemFunction("ArrayLength",
                new List<IVariableType>()
                {
                    new ArrayVariableType("a"),
                },
                SystemFunction_ArrayLength.Method);

            #endregion

            #region System Functions

            this.AddSystemFunction("SizeInBytes",
                new List<IVariableType>()
                {
                    new VarVariableType("v"),
                },
                SystemFunction_SizeInBytes.Method);

            #endregion

            #region IO Functions

            this.AddSystemFunction("FileAppendLine",
                new List<IVariableType>()
                {
                    new VarVariableType("fileName"),
                    new VarVariableType("content"),
                },
                SystemFunction_FileAppendLine.Method);

            #endregion

            #region String Functions

            this.AddSystemFunction("StringIsEmpty",
                new List<IVariableType>()
                {
                    new VarVariableType("s"),
                },
                SystemFunction_StringIsEmpty.Method);

            #endregion

            #endregion
        }

        #endregion
    }
}
